
import base64
import os
import tkinter as tk
from tkinter import ttk, messagebox
import threading
from queue import Queue
import webbrowser
import subprocess
import platform

# 导入新的管理器模块
from .gui_manager import GUIStateManager, LogManager, ThreadManager
from .utils import FileUtils
from .config import WINDOW_SIZE, LIST_FILE_NAME, LOST_FILE_NAME,HELP_FILE_NAME
from .gui_components import FolderSelector, GroupedControls, MovieListTable, LogDisplay, StatusBar, MainLayout
from .business_logic import MovieProcessor, ListManager
from .icon import img #导入icon图片的base64编码

class MovieListGUI:
    """
    TMDb电影刮削器主界面GUI类

    这是应用程序的主界面类，负责创建和管理整个GUI界面。
    提供电影链接收集、文件整理、NFO生成、海报下载等功能的图形界面。

    主要功能:
        - 电影文件夹选择和管理
        - 电影链接收集（步骤1）
        - 电影文件整理（步骤2）
        - NFO文件生成（步骤3）
        - 海报下载（步骤4）
        - 电影列表查看和编辑
        - 日志显示和进度跟踪
    """

    def __init__(self):
        """
        初始化TMDb电影刮削器主界面

        创建主窗口，初始化各种管理器和业务逻辑处理器，
        设置界面组件和线程间通信机制。

        Attributes:
            root (tk.Tk): 主窗口对象
            state_manager (GUIStateManager): GUI状态管理器
            log_manager (LogManager): 日志管理器
            thread_manager (ThreadManager): 线程管理器
            movie_processor (MovieProcessor): 电影处理业务逻辑
            list_manager (ListManager): 列表管理业务逻辑
            queue (Queue): 线程间通信队列
            folder_selector: 文件夹选择组件
            grouped_controls: 分组控制按钮组件
            movie_tree: 电影列表表格组件
            log_display: 日志显示组件
            status_bar: 状态栏组件
        """
        # 创建主窗口
        self.root = tk.Tk()
        self.root.title("TMDb 电影刮削器")

        #设置窗口图标
        tmp = open("tmp.ico","wb+")
        tmp.write(base64.b64decode(img))
        tmp.close() 
        self.root.iconbitmap("tmp.ico")
        os.remove("tmp.ico")
        

        # 设置窗口大小
        self.root.geometry(WINDOW_SIZE)

        # 居中显示窗口
        self.root.update_idletasks()
        # 解析窗口尺寸
        width_height = WINDOW_SIZE.split('x')
        width = int(width_height[0])
        height = int(width_height[1])
        x = (self.root.winfo_screenwidth() // 2) - (width // 2)
        y = (self.root.winfo_screenheight() // 2) - (height // 2)
        self.root.geometry(f"{width}x{height}+{x}+{y}")

        # 初始化各种管理器
        self.state_manager = GUIStateManager()  # 管理GUI状态和按钮状态
        self.log_manager = LogManager()  # 管理日志记录
        self.thread_manager = ThreadManager()  # 管理后台线程

        # 初始化业务逻辑处理器
        self.movie_processor = MovieProcessor(log_callback=self.log_message)  # 电影处理逻辑
        self.list_manager = ListManager(log_callback=self.log_message)  # 列表管理逻辑

        # 创建界面组件
        self.create_widgets()

        # 自动补充：将 grouped_controls 赋值给 movie_processor，确保进度条递增生效
        self.movie_processor.grouped_controls = self.grouped_controls

        # 初始化线程间通信队列
        self.queue = Queue()

        # 启动消息队列检查定时器（每100ms检查一次）
        self.root.after(100, self.check_queue)

    def create_widgets(self):
        """
        创建GUI界面的所有组件

        使用MainLayout组件来管理界面布局，简化主界面的创建过程。
        """
        # 创建主布局管理器
        self.main_layout = MainLayout(self.root, callbacks={
            'on_folder_selected': self.on_folder_selected,
            'organize_movies': self.organize_movies,
            'start_creating_list_processing': self.start_creating_list_processing,
            'generate_nfo_files': self.generate_nfo_files,
            'download_posters_from_nfo': self.download_posters_from_nfo,
            'show_help': self.show_help,
            'on_click': self.on_treeview_click,
            'on_double_click': self.on_treeview_double_click,
            'on_right_click': self.on_treeview_right_click,
            'edit_selected': self.edit_selected_item,
            'delete_selected': self.delete_selected_item,
            'add_new': self.add_new_item,
            'open_movie_list_file': self.open_movie_list_file,
            'open_lost_file': self.open_lost_file,
            'reset_gui': self.reset_gui
        })

        # 获取组件引用
        self.folder_selector = self.main_layout.get_folder_selector()
        self.grouped_controls = self.main_layout.get_grouped_controls()
        self.status_bar = self.main_layout.get_status_bar()

        # 优化：统一注册所有组件到管理器
        self.register_components()

    def register_components(self):
        """
        优化后的统一组件注册方法
        包括按钮、进度变量、日志组件等注册到各自管理器
        """
        # 注册所有按钮
        buttons = self.grouped_controls.get_all_buttons()
        buttons['reset'] = self.status_bar.get_reset_button()
        for name, button in buttons.items():
            self.state_manager.register_button(name, button)

        # 注册进度变量
        self.state_manager.register_progress_var(self.status_bar.get_progress_var())

        # 注册日志组件
        log_widget = self.grouped_controls.get_right_panel().get_log_display().get_widget()
        self.log_manager.register_log_widget(log_widget)

    def on_folder_selected(self, folder_path):
        """
        文件夹选择回调函数

        当用户选择文件夹时触发，自动加载该文件夹中的电影列表。

        Args:
            folder_path (str): 用户选择的文件夹路径
        """
        self.load_movie_list(folder_path)

    def browse_folder(self):
        """
        浏览文件夹（兼容性方法）

        提供向后兼容的文件夹浏览功能，实际调用文件夹选择器的方法。

        Returns:
            str: 选择的文件夹路径，如果取消则返回空字符串
        """
        return self.folder_selector.browse_folder()

    # 为了兼容性，保留这些属性的访问方法
    @property
    def folder_var(self):
        """
        获取文件夹变量（兼容性属性）

        Returns:
            tk.StringVar: 文件夹路径变量
        """
        return self.folder_selector.folder_var

    @property
    def movie_tree(self):
        """
        获取电影表格组件（兼容性属性）

        Returns:
            ttk.Treeview: 电影列表表格组件
        """
        return self.grouped_controls.get_right_panel().get_movie_table().movie_tree

    @property
    def context_menu(self):
        """
        获取右键菜单组件（兼容性属性）

        Returns:
            tk.Menu: 右键上下文菜单组件
        """
        return self.grouped_controls.get_right_panel().get_movie_table().context_menu

    @property
    def log_text(self):
        """
        获取日志文本组件（兼容性属性）

        Returns:
            scrolledtext.ScrolledText: 日志显示文本组件
        """
        return self.grouped_controls.get_right_panel().get_log_display().get_widget()

    @property
    def progress_var(self):
        """
        获取进度变量（兼容性属性）

        Returns:
            tk.StringVar: 进度显示变量
        """
        return self.status_bar.get_progress_var()

    def get_options(self):
        """
        获取用户设置的选项

        从分组控制组件中获取用户选择的各种选项设置。

        Returns:
            dict: 包含各种选项设置的字典，如：
                {
                    'delete_list': bool,      # 是否删除原有列表
                    'allow_overwrite': bool,  # 是否允许覆盖
                    'download_images': bool   # 是否下载图片
                }
        """
        return self.grouped_controls.get_options()



    def load_movie_list(self, folder_path):
        """
        加载List.txt文件内容到电影列表表格

        读取指定文件夹中的List.txt文件，解析其中的电影信息，
        并将其显示在电影列表表格中。同时检查列表与实际文件夹的一致性。

        Args:
            folder_path (str): 包含List.txt文件的文件夹路径

        功能:
            1. 清空现有表格内容
            2. 读取并解析List.txt文件
            3. 将电影信息添加到表格中
            4. 检查列表与实际文件夹的一致性
            5. 显示加载结果日志
        """
        # 获取电影表格
        movie_table = self.grouped_controls.get_right_panel().get_movie_table()
        
        # 清空现有表格内容
        movie_table.clear()

        # 构建List.txt文件路径
        list_file_path = os.path.join(folder_path, LIST_FILE_NAME)

        if os.path.exists(list_file_path):
            try:
                # 读取文件内容，过滤空行
                with open(list_file_path, 'r', encoding='utf-8') as f:
                    lines = [line.strip() for line in f.readlines() if line.strip()]

                # 解析每一行并添加到表格中
                for index, line in enumerate(lines, 1):
                    if '||' in line:  # 检查是否包含分隔符
                        name, url = line.split('||', 1)  # 分割电影名和URL
                        # 添加到表格中
                        movie_table.add_item(index, name.strip(), url.strip())

                # 显示加载成功日志
                self.log_message(f"✅ 已加载 {LIST_FILE_NAME}，共 {len(lines)} 条记录")

                # 检查清单文件与实际电影文件夹的一致性
                self.check_movie_list_consistency(folder_path, lines)

            except Exception as e:
                # 显示加载失败日志
                self.log_message(f"❌ 加载 {LIST_FILE_NAME} 失败: {e}")
        else:
            # 文件不存在时的提示
            self.log_message(f"⚠️ 未找到 {LIST_FILE_NAME} 文件")

    def check_movie_list_consistency(self, folder_path, list_lines):
        """检查清单文件与实际电影文件夹的一致性"""
        try:
            # 获取实际的电影文件夹
            actual_folders = []
            for item in os.listdir(folder_path):
                item_path = os.path.join(folder_path, item)
                if os.path.isdir(item_path):
                    actual_folders.append(item)

            # 从List.txt中提取电影名称
            list_movies = []
            for line in list_lines:
                if '||' in line:
                    movie_name, _ = line.split('||', 1)
                    list_movies.append(movie_name.strip())

            # 转换为集合进行比较
            actual_set = set(actual_folders)
            list_set = set(list_movies)

            # 检查是否完全一致
            if actual_set != list_set:
                # 计算差异
                only_in_folder = actual_set - list_set  # 只在文件夹中存在
                only_in_list = list_set - actual_set    # 只在清单中存在

                # 构建提示消息
                message_parts = ["当前清单文件与当前目录中的实有影片不一致："]

                if only_in_folder:
                    message_parts.append(f"\n📁 在实际文件夹中有，但在List.txt中没有的影片 ({len(only_in_folder)}部):")
                    message_parts.append("   （这些影片存在于目录中，但未记录在清单文件中）")
                    for movie in sorted(only_in_folder):
                        message_parts.append(f"   • {movie}")

                if only_in_list:
                    message_parts.append(f"\n📄 在List.txt文件中有，但实际在文件夹中没有的影片 ({len(only_in_list)}部):")
                    message_parts.append("   （这些影片记录在清单文件中，但目录中不存在对应文件夹）")
                    for movie in sorted(only_in_list):
                        message_parts.append(f"   • {movie}")

                message_parts.append(f"\n\n📊 详细统计信息:")
                message_parts.append(f"   • 实际文件夹中的影片总数: {len(actual_folders)}部")
                message_parts.append(f"   • List.txt清单中的记录总数: {len(list_movies)}部")
                message_parts.append(f"   • 两者都存在的匹配影片: {len(actual_set & list_set)}部")

                if only_in_folder and only_in_list:
                    message_parts.append(f"\n� 问题分析:")
                    message_parts.append(f"   • 可能原因1: 添加了新的电影文件夹但未更新清单")
                    message_parts.append(f"   • 可能原因2: 删除了电影文件夹但未更新清单")
                    message_parts.append(f"   • 可能原因3: 清单文件与当前目录不匹配")
                elif only_in_folder:
                    message_parts.append(f"\n🔄 问题分析:")
                    message_parts.append(f"   • 您可能添加了新的电影文件夹，但清单文件未同步更新")
                elif only_in_list:
                    message_parts.append(f"\n🔄 问题分析:")
                    message_parts.append(f"   • 您可能删除了电影文件夹，但清单文件未同步更新")

                message_parts.append(f"\n�💡 解决建议：")
                message_parts.append(f"   • 推荐操作: 重新爬取链接来更新清单文件")
                message_parts.append(f"   • 这将重新扫描目录并生成完整的清单文件")

                full_message = "".join(message_parts)

                # 弹窗提示用户
                messagebox.showwarning("清单文件不一致", full_message)

                # 记录到日志
                self.log_message(f"⚠️ 清单文件与目录不一致: 目录{len(actual_folders)}部，清单{len(list_movies)}部")
            else:
                # 一致的情况，记录到日志
                self.log_message(f"✅ 清单文件与目录一致: 共{len(actual_folders)}部影片")

        except Exception as e:
            self.log_message(f"❌ 检查清单一致性时出错: {e}")

    def log_message(self, message):
        """
        添加日志消息到线程安全队列

        将日志消息添加到队列中，由主线程的定时器定期检查并显示。
        这是线程安全的日志记录方法，可以从任何线程调用。

        Args:
            message (str): 要记录的日志消息

        Note:
            消息会被添加到队列中，由check_queue方法定期处理并显示在界面上。
        """
        self.queue.put(message)

    def check_queue(self):
        """
        检查消息队列并更新界面显示

        定期检查线程间通信队列中的消息，将消息显示在日志文本框中。
        这个方法通过定时器每100ms调用一次，确保界面及时更新。

        功能:
            1. 从队列中获取所有待处理的消息
            2. 将消息添加到日志文本框
            3. 自动滚动到最新消息
            4. 强制更新界面显示
            5. 设置下次检查的定时器

        Note:
            这是GUI线程安全更新的关键方法，确保后台线程的日志能正确显示。
        """
        try:
            # 处理队列中的所有消息
            while True:
                message = self.queue.get_nowait()  # 非阻塞获取消息
                self.log_text.insert(tk.END, message + "\n")  # 添加到日志文本框
                self.log_text.see(tk.END)  # 滚动到最新消息
                self.root.update_idletasks()  # 强制更新界面
        except:
            # 队列为空时会抛出异常，这是正常情况
            pass

        # 设置下次检查的定时器（100ms后再次检查）
        self.root.after(100, self.check_queue)

    def start_creating_list_processing(self):
        """
        开始电影链接收集处理（步骤1）

        启动电影链接收集流程，扫描电影文件夹并搜索每部电影的TMDb链接。
        这是整个工作流程的第一步。

        处理流程:
            1. 验证文件夹路径的有效性
            2. 清空日志显示区域
            3. 更新界面状态为处理中
            4. 获取用户选项设置
            5. 在后台线程中启动处理逻辑

        用户交互:
            - 如果未选择文件夹，显示错误对话框
            - 如果文件夹不存在，显示错误对话框
            - 处理过程中禁用相关按钮，显示进度

        Note:
            实际的处理逻辑在process_movies方法中执行，以避免阻塞GUI线程。
        """
        # 获取并验证文件夹路径
        folder_path = self.folder_selector.folder_var.get().strip()
        if not folder_path:
            messagebox.showerror("错误", "请选择电影文件夹")
            return

        if not os.path.exists(folder_path):
            messagebox.showerror("错误", "选择的文件夹不存在")
            return

        # 清空日志显示区域，准备显示新的处理日志
        log_widget = self.grouped_controls.get_right_panel().get_log_display().get_widget()
        log_widget.delete(1.0, tk.END)

        # 更新界面状态：禁用按钮，显示处理状态
        self.state_manager.start_creating_list_processing("正在处理...")
        self.grouped_controls.start_progress("start_creat_list", "正在收集电影链接...")

        # 获取用户选项设置
        options = self.get_options()
        delete_list = options['delete_list']

        # 在后台线程中启动处理逻辑，避免阻塞GUI
        self.processing_thread = threading.Thread(target=self.process_movies, args=(folder_path, delete_list))
        self.processing_thread.daemon = True  # 设置为守护线程
        self.processing_thread.start()

    def process_movies(self, folder_path, delete_list):
        """
        在后台线程中处理电影链接收集

        这是电影链接收集的核心处理方法，在后台线程中执行以避免阻塞GUI。
        扫描指定文件夹中的所有电影子文件夹，搜索每部电影的TMDb链接。

        Args:
            folder_path (str): 要处理的电影文件夹路径
            delete_list (bool): 是否删除已存在的List.txt文件

        处理流程:
            1. 创建业务逻辑控制器和进度回调函数
            2. 调用业务逻辑层的电影链接收集方法
            3. 处理完成后更新界面状态
            4. 异常处理和错误日志记录

        线程安全:
            - 使用root.after()确保GUI更新在主线程中执行
            - 通过队列机制安全地记录日志消息

        Note:
            这个方法运行在后台线程中，不能直接操作GUI组件。
            所有GUI更新都通过root.after()调度到主线程执行。
        """
        try:
            from .business_logic import MovieWorkflowController

            # 创建进度回调函数，确保进度更新在主线程中执行
            def progress_callback(progress, message):
                self.root.after(0, lambda: self.grouped_controls.set_progress("start_creat_list", progress, message))

            # 创建工作流控制器，传入日志回调
            controller = MovieWorkflowController(log_callback=self.log_message)

            # 准备处理选项
            options = {'delete_list': delete_list}

            # 调用业务逻辑层执行实际的链接收集工作
            success_count, failed_count = controller.collect_movie_links(
                folder_path, options, progress_callback, self.root
            )

            # 记录处理完成日志
            self.log_message("🎉 处理完成!")

            # 在主线程中执行完成后的界面更新
            self.root.after(0, self.processing_finished)

        except Exception as error:
            # 记录错误日志
            self.log_message(f"❌ 处理过程中出错: {error}")

            # 即使出错也要恢复界面状态
            self.root.after(0, self.processing_finished)

    def processing_finished(self):
        """处理完成后的界面更新"""
        # 恢复按钮状态
        self.state_manager.finish_processing()
        self.grouped_controls.complete_progress("start_creat_list", f"收集完成，可以对电影列表进行编辑，或直接修改{LIST_FILE_NAME}")
        self.log_message("✅ 所有电影处理完成！")
        # 生成完成后自动加载列表
        self.load_movie_list(self.folder_var.get())

    def organize_movies(self):
        """整理电影文件"""
        folder_path = self.folder_var.get().strip()
        if not folder_path:
            messagebox.showwarning("警告", "请先选择电影文件夹")
            return

        if not os.path.exists(folder_path):
            messagebox.showerror("错误", "选择的文件夹不存在")
            return

        # 确认对话框
        if not messagebox.askyesno("确认",
                                   "此操作将整理指定文件夹中的电影文件，为每个电影创建单独的文件夹。\n\n"
                                   "支持的格式: '.mp4', '.mkv', '.rmvb', '.avi', '.mov', '.wmv', '.flv'\n"
                                   "文件夹命名规则: 取视频文件名第一个'.'前面部分\n\n"
                                   "是否继续？"):
            return

        # 在后台线程中执行整理操作
        self.state_manager.set_processing_state(["organize"], "正在整理电影文件...")
        self.grouped_controls.set_progress("organize", 0, "正在扫描文件...")

        def organize_thread():
            try:
                from .business_logic import MovieWorkflowController

                # 创建进度回调函数
                def progress_callback(progress, message):
                    self.root.after(0, lambda: self.grouped_controls.set_progress("organize", progress, message))

                # 创建工作流控制器
                controller = MovieWorkflowController(log_callback=self.log_message)

                # 调用业务逻辑层
                organized_count, skipped_count = controller.organize_movie_files(
                    folder_path, progress_callback
                )

                self.log_message(f"🎉 电影文件整理完成! 已整理: {organized_count} 个文件，跳过: {skipped_count} 个文件")
                self.root.after(0, lambda: self.grouped_controls.complete_progress("organize", f"整理完成 ({organized_count}/{organized_count + skipped_count})"))

            except Exception as e:
                self.log_message(f"❌ 整理过程中出错: {e}")
                self.root.after(0, lambda: self.grouped_controls.stop_progress("organize", "整理失败"))
            finally:
                self.root.after(0, lambda: self.state_manager.finish_specific_processing(["organize"]))

        threading.Thread(target=organize_thread, daemon=True).start()

    def generate_nfo_files(self):
        """生成NFO文件"""
        folder_path = self.folder_var.get().strip()
        if not folder_path:
            messagebox.showerror("错误", "请选择电影文件夹")
            return

        if not os.path.exists(folder_path):
            messagebox.showerror("错误", "选择的文件夹不存在")
            return

        # 检查List.txt是否存在
        list_file_path = os.path.join(folder_path, LIST_FILE_NAME)
        if not os.path.exists(list_file_path):
            messagebox.showerror("错误", f"未找到{LIST_FILE_NAME}文件，请先生成电影清单")
            return

        # 确认对话框
        if not messagebox.askyesno("确认",
                                   "此操作将根据List.txt文件批量生成NFO文件到各电影文件夹。\n\n"
                                   "是否继续？"):
            return

        # 清空日志
        self.log_text.delete(1.0, tk.END)

        # 更新界面状态
        # modify
        self.state_manager.set_processing_state(
            ["start_creat_list", "generate_nfo", "organize", "download_from_nfo","reset"],
            "正在生成NFO文件..."
        )
        
        self.grouped_controls.start_progress("generate_nfo", "正在生成NFO文件...")

        # 获取选项设置
        options = self.get_options()

        # 在新线程中运行NFO生成逻辑
        self.nfo_thread = threading.Thread(target=self.process_nfo_generation, args=(folder_path, options))
        self.nfo_thread.daemon = True
        self.nfo_thread.start()

    def process_nfo_generation(self, folder_path, options):
        """在后台线程中生成NFO文件"""
        try:
            from .business_logic import MovieWorkflowController

            # 创建进度回调函数
            def progress_callback(progress, message):
                self.root.after(0, lambda: self.grouped_controls.set_progress("generate_nfo", progress, message))

            # 创建工作流控制器
            controller = MovieWorkflowController(log_callback=self.log_message)

            # 调用业务逻辑层
            success_count, failed_count, skipped_count = controller.generate_nfo_files_workflow(
                folder_path, options, progress_callback
            )

            self.log_message(f"\n🎉 NFO文件生成完成!")
            self.log_message(f"📊 统计: 成功 {success_count} 个，失败 {failed_count} 个")
            if not options.get('allow_overwrite', False) and skipped_count > 0:
                self.log_message(f"   跳过 {skipped_count} 个已存在的文件")
            self.root.after(0, self.nfo_generation_finished)

        except Exception as error:
            self.log_message(f"❌ NFO生成过程中出错: {error}")
            self.root.after(0, self.nfo_generation_finished)

    def get_largest_video_file(self, movie_folder_path):
        """获取电影文件夹中尺寸最大的视频文件名（不含扩展名）"""
        return FileUtils.get_largest_video_file(movie_folder_path)

    def nfo_generation_finished(self):
        """NFO生成完成后的界面更新"""
        # 恢复按钮状态
        self.state_manager.finish_specific_processing(
            ["start_creat_list", "generate_nfo", "organize", "download_from_nfo","reset"]
        )
        self.grouped_controls.complete_progress("generate_nfo", "生成完成，可以在KODI中添加视频源了")
        self.log_message("✅ NFO文件生成完成！")
        # 生成完成后自动加载列表
        self.load_movie_list(self.folder_var.get())

    def nfo_download_finished(self):
        """NFO海报下载完成后的界面更新"""
        # 恢复按钮状态
        self.state_manager.finish_specific_processing(
            ["start_creat_list", "generate_nfo", "organize", "download_from_nfo","reset"]
        )
        self.grouped_controls.complete_progress("download_from_nfo", "下载完成")
        self.log_message("✅ 海报下载完成！")
        # 下载完成后自动加载列表
        self.load_movie_list(self.folder_var.get())

    def open_movie_list_file(self):
        """打开电影清单文件 (List.txt)"""
        folder_path = self.folder_var.get().strip()
        if not folder_path:
            messagebox.showwarning("提示", "请先选择电影文件夹")
            return

        list_file_path = os.path.join(folder_path, LIST_FILE_NAME)

        if not os.path.exists(list_file_path):
            messagebox.showinfo("提示", f"电影清单文件不存在：\n{list_file_path}")
            return

        try:
            # 使用系统默认程序打开文件
            if platform.system() == 'Windows':
                os.startfile(list_file_path)
            elif platform.system() == 'Darwin':  # macOS
                subprocess.run(['open', list_file_path])
            else:  # Linux
                subprocess.run(['xdg-open', list_file_path])

            self.log_message(f"✅ 已打开电影清单文件: {list_file_path}")

        except Exception as e:
            messagebox.showerror("错误", f"打开文件失败：\n{e}")
            self.log_message(f"❌ 打开电影清单文件失败: {e}")

    def open_lost_file(self):
        """打开错误日志文件 (lost.txt)"""
        folder_path = self.folder_var.get().strip()
        if not folder_path:
            messagebox.showwarning("提示", "请先选择电影文件夹")
            return

        lost_file_path = os.path.join(folder_path, LOST_FILE_NAME)

        if not os.path.exists(lost_file_path):
            messagebox.showinfo("提示", f"错误日志文件不存在：\n{lost_file_path}")
            return

        try:
            # 使用系统默认程序打开文件
            if platform.system() == 'Windows':
                os.startfile(lost_file_path)
            elif platform.system() == 'Darwin':  # macOS
                subprocess.run(['open', lost_file_path])
            else:  # Linux
                subprocess.run(['xdg-open', lost_file_path])

            self.log_message(f"✅ 已打开错误日志文件: {lost_file_path}")

        except Exception as e:
            messagebox.showerror("错误", f"打开文件失败：\n{e}")
            self.log_message(f"❌ 打开错误日志文件失败: {e}")

    def show_help(self):
        """显示使用说明"""
        help_window = tk.Toplevel(self.root)
        help_window.title("使用说明")
        help_window.geometry("700x500")
        help_window.transient(self.root)
        help_window.grab_set()
        
        # 居中显示
        help_window.geometry("+%d+%d" % (self.root.winfo_rootx() + 50, self.root.winfo_rooty() + 50))
        
        # 创建主框架
        main_frame = ttk.Frame(help_window, padding="20")
        main_frame.pack(fill=tk.BOTH, expand=True)
        
        # 标题
        title_label = ttk.Label(main_frame, text="TMDb 电影利削器 - 使用说明", 
                               font=('', 14, 'bold'))
        title_label.pack(pady=(0, 20))
        
        # 创建滚动文本框
        text_frame = ttk.Frame(main_frame)
        text_frame.pack(fill=tk.BOTH, expand=True)
        
        scrollbar = ttk.Scrollbar(text_frame)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        
        help_text = tk.Text(text_frame, wrap=tk.WORD, yscrollcommand=scrollbar.set, 
                           font=('', 10), padx=10, pady=10)
        help_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.config(command=help_text.yview)
        
        # 读取帮助文件内容
        try:
            # 获取项目根目录路径（modules的上级目录）
            current_dir = os.path.dirname(__file__)  # modules目录
            project_root = os.path.dirname(current_dir)  # 项目根目录
            help_file_path = os.path.join(project_root, HELP_FILE_NAME)
            
            with open(help_file_path, 'r', encoding='utf-8') as f:
                help_content = f.read()
        except Exception as e:
            help_content = f"无法读取帮助文件: {e}\n\n请确保帮助文件存在于程序目录中。"

        help_text.insert(tk.END, help_content)
        help_text.config(state=tk.DISABLED)  # 设置为只读
        
        # 关闭按钮
        close_button = ttk.Button(main_frame, text="关闭", command=help_window.destroy)
        close_button.pack(pady=(20, 0))

    def reset_gui(self):
        """
        重置GUI界面

        重新加载主窗体，恢复到初始状态。
        这会清空所有数据、日志和状态，重新初始化界面。
        """
        try:
            # 确认对话框
            if not messagebox.askyesno("确认重置",
                                     "此操作将重置整个界面，清空所有数据和日志。\n\n"
                                     "是否确定要重置？"):
                return

            # 清理已完成的线程
            if hasattr(self, 'thread_manager'):
                self.thread_manager.cleanup_finished_threads()

            # 记录重置操作
            self.log_message("🔄 正在重置界面...")

            # 清空电影列表表格
            movie_table = self.grouped_controls.get_right_panel().get_movie_table()
            movie_table.clear()

            # 清空日志显示
            log_widget = self.grouped_controls.get_right_panel().get_log_display().get_widget()
            log_widget.delete(1.0, tk.END)

            # 重置文件夹选择
            if hasattr(self, 'folder_selector'):
                self.folder_selector.folder_var.set("")

            # 重置所有管理器状态
            if hasattr(self, 'state_manager'):
                self.state_manager.finish_processing()
                self.state_manager.reset_progress()

            # 重置分组控制组件的进度
            if hasattr(self, 'grouped_controls'):
                # 重置所有进度条
                for progress_name in ['start_creat_list', 'organize', 'generate_nfo', 'download_from_nfo']:
                    try:
                        self.grouped_controls.stop_progress(progress_name, "")
                    except:
                        pass  # 忽略不存在的进度条

            # 记录重置完成
            self.log_message("✅ 界面重置完成！")
            self.log_message("💡 请重新选择电影文件夹开始使用")

        except Exception as e:
            messagebox.showerror("重置失败", f"重置界面时发生错误：\n{e}")
            self.log_message(f"❌ 重置界面失败: {e}")



    def on_treeview_click(self, event):
        """
        处理电影列表表格的单击事件

        当用户点击表格中的TMDb链接列时，自动在浏览器中打开对应的链接。
        只有点击第3列（TMDb链接列）时才会触发链接打开操作。

        Args:
            event: Tkinter事件对象，包含鼠标点击的位置信息

        功能:
            1. 识别点击的区域和列
            2. 检查是否点击了TMDb链接列
            3. 获取选中项的URL
            4. 在浏览器中打开链接
            5. 记录操作日志

        异常处理:
            - 捕获浏览器打开失败的异常
            - 记录错误日志
        """
        # 获取鼠标点击的区域类型
        region = self.movie_tree.identify_region(event.x, event.y)
        if region == "cell":
            # 获取点击的列索引（从1开始）
            column = int(self.movie_tree.identify_column(event.x).replace("#", ""))
            if column == 3:  # 第3列是TMDb链接列
                # 获取当前选中的表格项
                selected_items = self.movie_tree.selection()
                if selected_items:
                    item = selected_items[0]
                    # 获取该项的URL值（索引2对应第3列）
                    url = self.movie_tree.item(item, "values")[2]
                    if url:
                        try:
                            # 在默认浏览器中打开链接
                            webbrowser.open(url)
                            self.log_message(f"🌐 已打开链接: {url}")
                        except Exception as e:
                            self.log_message(f"❌ 打开链接失败: {e}")

    def on_treeview_double_click(self, event):
        """
        处理电影列表表格的双击事件

        当用户双击表格中的任意项时，打开编辑对话框允许用户修改电影信息。

        Args:
            event: Tkinter事件对象（未使用，但必须接收）

        功能:
            - 检查是否有选中的项
            - 调用编辑功能
        """
        selected_items = self.movie_tree.selection()
        if selected_items:
            self.edit_selected_item()

    def on_treeview_right_click(self, event):
        """处理表格右键点击事件，显示上下文菜单"""
        # 选中右键点击的项目
        item = self.movie_tree.identify_row(event.y)
        if item:
            self.movie_tree.selection_set(item)
            self.context_menu.post(event.x_root, event.y_root)

    def edit_selected_item(self):
        """编辑选中的条目"""
        selected_items = self.movie_tree.selection()
        if not selected_items:
            messagebox.showwarning("警告", "请先选择要编辑的条目")
            return

        item = selected_items[0]
        values = self.movie_tree.item(item, "values")
        if len(values) >= 3:
            index, name, url = values[0], values[1], values[2]

            # 创建编辑对话框
            dialog = MovieEditDialog(self.root, name, url)
            if dialog.result:
                new_name, new_url = dialog.result
                # 更新表格
                self.movie_tree.item(item, values=(index, new_name, new_url))
                # 保存到文件
                self.save_list_to_file()
                self.log_message(f"✅ 已更新条目: {new_name}")

    def delete_selected_item(self):
        """删除选中的条目"""
        selected_items = self.movie_tree.selection()
        if not selected_items:
            messagebox.showwarning("警告", "请先选择要删除的条目")
            return

        item = selected_items[0]
        values = self.movie_tree.item(item, "values")
        if len(values) >= 2:
            name = values[1]

            # 确认删除
            if messagebox.askyesno("确认删除", f"确定要删除条目 '{name}' 吗？"):
                self.movie_tree.delete(item)
                # 重新编号
                self.renumber_items()
                # 保存到文件
                self.save_list_to_file()
                self.log_message(f"✅ 已删除条目: {name}")

    def add_new_item(self):
        """添加新条目"""
        # 创建编辑对话框
        dialog = MovieEditDialog(self.root, "", "")
        if dialog.result:
            new_name, new_url = dialog.result
            # 获取新的序号
            children = self.movie_tree.get_children()
            new_index = len(children) + 1
            # 添加到表格
            self.movie_tree.insert('', tk.END, values=(new_index, new_name, new_url), tags=('url_link',))
            # 保存到文件
            self.save_list_to_file()
            self.log_message(f"✅ 已添加新条目: {new_name}")

    def renumber_items(self):
        """重新编号表格项目"""
        children = self.movie_tree.get_children()
        for index, item in enumerate(children, 1):
            values = list(self.movie_tree.item(item, "values"))
            values[0] = index  # 更新序号
            self.movie_tree.item(item, values=values)

    def save_list_to_file(self):
        """保存表格内容到List.txt文件"""
        folder_path = self.folder_var.get().strip()
        if not folder_path:
            return

        list_file_path = os.path.join(folder_path, LIST_FILE_NAME)
        try:
            with open(list_file_path, 'w', encoding='utf-8') as f:
                children = self.movie_tree.get_children()
                for item in children:
                    values = self.movie_tree.item(item, "values")
                    if len(values) >= 3:
                        name, url = values[1], values[2]
                        f.write(f"{name}||{url}\n")

            self.log_message(f"💾 已保存到 {LIST_FILE_NAME}，共 {len(children)} 条记录")
        except Exception as e:
            self.log_message(f"❌ 保存 {LIST_FILE_NAME} 失败: {e}")
            messagebox.showerror("错误", f"保存失败: {e}")



    def download_posters_from_nfo(self):
        """根据NFO文件下载海报"""
        folder_path = self.folder_var.get().strip()
        if not folder_path:
            messagebox.showerror("错误", "请选择电影文件夹")
            return

        if not os.path.exists(folder_path):
            messagebox.showerror("错误", "选择的文件夹不存在")
            return

        # 确认对话框
        if not messagebox.askyesno("确认",
                                   "此操作将扫描所有电影文件夹中的NFO文件，\n"
                                   "并根据NFO中的链接下载缺失的海报和同人画。\n\n"
                                   "是否继续？"):
            return

        # 清空日志
        self.log_text.delete(1.0, tk.END)

        # 更新界面状态
        self.state_manager.set_processing_state(
            ["start_creat_list", "generate_nfo", "organize", "download_from_nfo","reset"],
            "正在根据NFO文件下载海报..."
        )
        self.grouped_controls.start_progress("download_from_nfo", "正在下载海报...")

        # 在新线程中运行下载逻辑
        self.nfo_download_thread = threading.Thread(target=self.process_nfo_poster_download_wrapper, args=(folder_path,))
        self.nfo_download_thread.daemon = True
        self.nfo_download_thread.start()

    def process_nfo_poster_download_wrapper(self, folder_path):
        """包装函数：在后台线程中根据NFO文件下载海报"""
        try:
            from .business_logic import MovieProcessor

            # 创建进度回调函数
            def progress_callback(progress, message):
                self.root.after(0, lambda: self.grouped_controls.set_progress("download_from_nfo", progress, message))

            # 创建业务逻辑处理器
            processor = MovieProcessor(log_callback=self.log_message)

            # 调用业务逻辑层的方法
            success_count, failed_count, skipped_count = processor.download_posters_from_nfo(
                folder_path, progress_callback
            )

            self.root.after(0, self.nfo_download_finished)

        except Exception as error:
            self.log_message(f"❌ 处理过程出错: {error}")
            self.root.after(0, self.nfo_download_finished)


class MovieChoiceDialog:
    """
    电影选择对话框类

    当搜索电影时找到多个匹配结果时，显示选择对话框让用户选择正确的电影。
    这是一个模态对话框，会阻塞主界面直到用户做出选择。
    """

    def __init__(self, parent, movie_name, results):
        """
        初始化电影选择对话框

        Args:
            parent: 父窗口对象，用于设置对话框的父子关系
            movie_name (str): 要选择的电影名称，显示在对话框标题中
            results (list): 搜索结果列表，每个元素包含电影信息字典
                          格式: [{'title': '电影标题', 'year': '年份', 'url': '链接'}, ...]

        Attributes:
            result (int): 用户选择结果，0表示跳过，1-N表示选择第N个结果
            dialog (tk.Toplevel): 对话框窗口对象
        """
        self.result = 0  # 默认跳过，0表示跳过，1-N表示选择第N个结果

        # 创建模态对话框窗口
        self.dialog = tk.Toplevel(parent)
        self.dialog.title(f"选择电影: {movie_name}")
        self.dialog.geometry("600x400")
        self.dialog.transient(parent)  # 设置为父窗口的子窗口
        self.dialog.grab_set()  # 设置为模态对话框

        # 居中显示对话框
        self.dialog.geometry("+%d+%d" % (parent.winfo_rootx() + 50, parent.winfo_rooty() + 50))

        # 创建界面元素
        self.create_widgets(movie_name, results)

        # 等待用户选择（阻塞直到对话框关闭）
        self.dialog.wait_window()

    def create_widgets(self, movie_name, results):
        """
        创建对话框界面元素

        构建包含电影选择列表、按钮等界面组件的对话框。
        界面包括：标题、可滚动的电影列表、确定/跳过按钮。

        Args:
            movie_name (str): 电影名称，用于显示在标题中
            results (list): 搜索结果列表，包含电影信息字典

        UI组件:
            - 标题标签：显示电影名称和提示信息
            - 列表框：显示所有搜索结果供用户选择
            - 滚动条：当结果较多时提供滚动功能
            - 按钮：确定和跳过按钮
        """
        # 创建主框架
        main_frame = ttk.Frame(self.dialog, padding="10")
        main_frame.pack(fill=tk.BOTH, expand=True)

        # 创建标题标签
        title_label = ttk.Label(main_frame, text=f'"{movie_name}" 找到多个匹配结果:', font=('', 12, 'bold'))
        title_label.pack(pady=(0, 10))

        # 创建列表框架（包含列表和滚动条）
        list_frame = ttk.Frame(main_frame)
        list_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))

        # 创建垂直滚动条
        scrollbar = ttk.Scrollbar(list_frame)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        # 创建列表框，关联滚动条
        self.listbox = tk.Listbox(list_frame, yscrollcommand=scrollbar.set, font=('', 10))
        self.listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.config(command=self.listbox.yview)

        # 添加"跳过"选项作为第一项
        self.listbox.insert(tk.END, "0. 跳过此电影")

        # 添加所有搜索结果到列表框
        for index, result in enumerate(results):
            year_str = f" ({result['year']})" if result['year'] else ''
            self.listbox.insert(tk.END, f"{index + 1}. {result['title']}{year_str}")

        # 默认选择第一个电影结果（索引1，跳过"跳过此电影"选项）
        if results:
            self.listbox.selection_set(1)

        # 创建按钮框架
        button_frame = ttk.Frame(main_frame)
        button_frame.pack(pady=(10, 0))

        # 创建确定和跳过按钮
        ttk.Button(button_frame, text="确定", command=self.ok_clicked).pack(side=tk.LEFT, padx=(0, 10))
        ttk.Button(button_frame, text="跳过", command=self.skip_clicked).pack(side=tk.LEFT)

        # 绑定双击事件，双击列表项等同于点击确定
        self.listbox.bind('<Double-Button-1>', lambda _: self.ok_clicked())

    def ok_clicked(self):
        """
        确定按钮点击事件处理

        获取用户在列表框中选择的项目索引，并将结果保存到self.result中。
        然后关闭对话框。

        Returns:
            None

        Side Effects:
            - 设置self.result为选中项的索引（0表示跳过，1-N表示选择第N个电影）
            - 销毁对话框窗口
        """
        selection = self.listbox.curselection()
        if selection:
            self.result = selection[0]  # 获取选中项的索引
        self.dialog.destroy()  # 关闭对话框

    def skip_clicked(self):
        """
        跳过按钮点击事件处理

        设置结果为0（表示跳过），然后关闭对话框。

        Returns:
            None

        Side Effects:
            - 设置self.result为0（跳过）
            - 销毁对话框窗口
        """
        self.result = 0  # 设置为跳过
        self.dialog.destroy()  # 关闭对话框


class MovieEditDialog:
    """
    电影条目编辑对话框类

    用于编辑电影列表中的条目信息，包括电影名称和URL。
    提供文本输入框让用户修改电影信息，并返回修改结果。
    """

    def __init__(self, parent, name, url):
        """
        初始化电影编辑对话框

        Args:
            parent: 父窗口对象
            name (str): 当前电影名称
            url (str): 当前电影URL

        Attributes:
            result (tuple or None): 编辑结果，格式为(name, url)，None表示取消
            dialog (tk.Toplevel): 对话框窗口对象
        """
        self.result = None  # 存储编辑结果，None表示取消，tuple表示修改后的(name, url)

        # 创建模态对话框窗口
        self.dialog = tk.Toplevel(parent)
        self.dialog.title("编辑电影条目")
        self.dialog.geometry("600x200")
        self.dialog.transient(parent)  # 设置为父窗口的子窗口
        self.dialog.grab_set()  # 设置为模态对话框

        # 居中显示对话框
        self.dialog.geometry("+%d+%d" % (parent.winfo_rootx() + 50, parent.winfo_rooty() + 50))

        # 创建界面元素
        self.create_widgets(name, url)

        # 等待用户操作（阻塞直到对话框关闭）
        self.dialog.wait_window()

    def create_widgets(self, name, url):
        """
        创建编辑对话框的界面元素

        构建包含电影名称和URL输入框的编辑界面。

        Args:
            name (str): 当前电影名称，用于初始化输入框
            url (str): 当前电影URL，用于初始化输入框

        UI组件:
            - 电影名称输入框：允许用户修改电影名称
            - TMDb链接输入框：允许用户修改电影URL
            - 确定/取消按钮：保存或取消修改
        """
        # 创建主框架
        main_frame = ttk.Frame(self.dialog, padding="20")
        main_frame.pack(fill=tk.BOTH, expand=True)

        # 创建电影名称标签和输入框
        ttk.Label(main_frame, text="电影名称:").grid(row=0, column=0, sticky=tk.W, pady=(0, 10))
        self.name_var = tk.StringVar(value=name)  # 绑定变量，初始值为当前名称
        name_entry = ttk.Entry(main_frame, textvariable=self.name_var, width=60)
        name_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), pady=(0, 10))
        name_entry.focus()  # 设置焦点到名称输入框

        # 创建TMDb链接标签和输入框
        ttk.Label(main_frame, text="TMDb链接:").grid(row=1, column=0, sticky=tk.W, pady=(0, 20))
        self.url_var = tk.StringVar(value=url)  # 绑定变量，初始值为当前URL
        url_entry = ttk.Entry(main_frame, textvariable=self.url_var, width=60)
        url_entry.grid(row=1, column=1, sticky=(tk.W, tk.E), pady=(0, 20))

        # 创建按钮框架
        button_frame = ttk.Frame(main_frame)
        button_frame.grid(row=2, column=0, columnspan=2, pady=(10, 0))

        # 创建确定按钮
        ok_button = ttk.Button(button_frame, text="确定", command=self.ok_clicked)
        ok_button.pack(side=tk.LEFT, padx=(0, 10))

        # 创建取消按钮
        cancel_button = ttk.Button(button_frame, text="取消", command=self.cancel_clicked)
        cancel_button.pack(side=tk.LEFT)

        # 配置列权重，使输入框可以拉伸
        main_frame.columnconfigure(1, weight=1)

        # 绑定键盘快捷键
        self.dialog.bind('<Return>', lambda e: self.ok_clicked())  # 回车键确定
        self.dialog.bind('<Escape>', lambda e: self.cancel_clicked())  # ESC键取消

    def ok_clicked(self):
        """
        确定按钮点击事件处理

        验证用户输入的电影名称和URL，如果验证通过则保存结果并关闭对话框。
        如果验证失败则显示警告信息。

        Returns:
            None

        Side Effects:
            - 验证输入内容的有效性
            - 设置self.result为(name, url)元组或保持None
            - 可能显示警告对话框
            - 成功时销毁对话框窗口
        """
        # 获取并清理用户输入
        name = self.name_var.get().strip()
        url = self.url_var.get().strip()

        # 验证电影名称不能为空
        if not name:
            messagebox.showwarning("警告", "请输入电影名称")
            return

        # 验证TMDb链接不能为空
        if not url:
            messagebox.showwarning("警告", "请输入TMDb链接")
            return

        # 保存编辑结果并关闭对话框
        self.result = (name, url)
        self.dialog.destroy()

    def cancel_clicked(self):
        """
        取消按钮点击事件处理

        取消编辑操作，不保存任何修改，直接关闭对话框。

        Returns:
            None

        Side Effects:
            - 设置self.result为None（表示取消）
            - 销毁对话框窗口
        """
        self.result = None  # 设置为None表示取消编辑
        self.dialog.destroy()  # 关闭对话框
